#pragma once 

#include <cassert>
#include <type_traits>
#include <utility> //for std::forwars

struct NullOptType
{
};

constexpr NullOptType NullOpt{};



template<typename T>
class  TOptional //: public CReflectableOptional
{
    T* Data = nullptr;

public:
    TOptional(NullOptType) {}

    TOptional() {};

    TOptional(const T& InValue)
    {
        Data = new T(InValue);
    }

    TOptional(T&& InValue)
    {
        Data = new T(std::forward<T>(InValue));
    }

    TOptional(const TOptional& InValue)
    {
        if(InValue.HasValue())
        {
            Data = new T(InValue.GetValue());
        }
    }

    TOptional(TOptional&& InValue)
    {
        Data = InValue.Data;
        InValue.Data = nullptr;
    }


    TOptional& operator =(const T& InValue)
    {   
        if(Data)
        {
            delete (T*)Data;
        }
        Data = new T(InValue);
        return *this;
    }

    TOptional& operator =(T&& InValue)
    {
        if(Data)
        {
            delete (T*)Data;
        }
        Data =  new T(std::forward<T>(InValue));
        return *this;
    }

    TOptional& operator =(const TOptional& InValue)
    {
        if (&InValue == this)
        {
            return  *this;
        }
        if(Data)
        {
            delete (T*)Data;
        }

        if(InValue.HasValue())
        {
            Data = new T(InValue.GetValue());
        }
        else
        {
            Data = nullptr;
        }
        return *this;
    }

    TOptional& operator =(TOptional&& InValue)
    {
        if(Data)
        {
            delete (T*)Data;
        }
        Data = std::move(InValue.Data);
        InValue.Data = nullptr;
        return *this;
    }

    ~TOptional() 
    {
        if (Data)
        {
            delete (T*)Data;
            Data = nullptr;
        }
    }

    bool HasValue() const
    {
        return (bool)Data;
    }

     operator bool() const
    {
        return HasValue();
    }

    T& GetValue()
    {
        assert(HasValue());
        return *(T*)Data;
    }
    const T& GetValue() const 
    {
        assert(HasValue());
        return *(T*)Data;
    }

    T& operator *()
    {
        return GetValue();
    }

    const T& operator *() const 
    {
        return GetValue();
    }

    T* operator ->()
    {
        assert(Data);
        return (T*)Data;
    }

    const T* operator ->() const
    {
        assert(Data);
        return (T*)Data;
    }

    bool operator==(const TOptional& Other) const 
    {
        if (Other.HasValue() != HasValue())
        {
            return false;
        }
        if (HasValue())
        {
            return true;
        }

        return GetValue() == GetValue();
    }


    void Reset()
    {
        if(Data)
        {
            delete (T*)Data;
            Data=nullptr;
        }
    }
};
